package cc.iotkit.model.interceptors;

import cc.iotkit.common.satoken.utils.LoginHelper;
import cc.iotkit.common.utils.SpringUtils;
import cc.iotkit.common.utils.StringUtils;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.select.*;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.*;

/***
 * 数据权限拦截器
 * 必要条件：
 * 1.实体类必需添加@Commpent注解，否则无法获取bean实例
 * 2.部门权限字段:create_dept、用户权限字段：uid
 * 3.实体类名必须以Tb开头，如：TbDeviceInfo.java，否则无法获取bean实例
 */
@Slf4j
@Data
@Accessors(chain = true)
@SuppressWarnings("ALL")
public class DataPermissionInterceptor implements StatementInspector {
    //private Set<String> roleDeptPermission;
    private static ThreadLocal<Set<String>> roleDeptPermission = new ThreadLocal<>();
    private String deptIdColumn = "create_dept";
    private String userIdColumn="uid";

    private String tenantIdColumn="tenant_id";

    //需要忽略的方法
    private HashSet<String> ignoreMethods= new HashSet<>();
    //需要忽略的部门权限的实体
    private HashSet<String> ignoreDeptEntitys= new HashSet<>();

    //设置忽略的方法，下列方法不执行sql拼接
    public DataPermissionInterceptor(){
        //ignoreMethods.add("cc.iotkit.system.controller.SysUserController.getInfo");
        ignoreMethods.add("cc.iotkit.web.controller.AuthController.login");
        ignoreMethods.add("cc.iotkit.data.service.SysUserDataImpl.findById");
        ignoreMethods.add("cc.iotkit.data.service.SysRoleDataImpl.selectRolePermissionByUserId");
        ignoreMethods.add("cc.iotkit.data.service.SysRoleDataImpl.getUserRoles");
        //ignoreDeptEntitys.add("device_info");
        ignoreDeptEntitys.add("product");
    }

    /**
     * 重写StatementInspector的inspect接口，参数为hibernate处理后的原始SQL，返回值为我们修改后的SQL
     *
     * @param sql
     * @return
     */
    @Override
    public String inspect(String sql) {
        try {
            //超级管理员或系统进程调用
            if(LoginHelper.isSuperAdmin()|| RequestContextHolder.getRequestAttributes()==null) return null;

            //是否需要权限过滤
            boolean isNeedAuthority=true;

            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            for (StackTraceElement element : stackTrace) {
                    String clazz = element.getClassName();
                    String methodName=clazz+"."+ element.getMethodName();
                    if (ignoreMethods.contains(methodName)) {
                        // 当前调用类是被排除的类，返回空字符串不记录
                        isNeedAuthority=false;
                        break;
                    }
            }
            //无需权限过滤
            if(!isNeedAuthority) return sql;
            roleDeptPermission.set(LoginHelper.getLoginUser().getRoleDeptPermission());
            log.info("roleDeptPermission:"+roleDeptPermission.get().toString());
            log.info("组织筛选解析开始，原始SQL：{}", sql);
            Statements statements = CCJSqlParserUtil.parseStatements(sql);
            StringBuilder sqlStringBuilder = new StringBuilder();
            int i = 0;
            for (Statement statement : statements.getStatements()) {
                if (null != statement) {
                    if (i++ > 0) {
                        sqlStringBuilder.append(';');
                    }
                    sqlStringBuilder.append(this.processParser(statement));
                }
            }
            String newSql = sqlStringBuilder.toString();
            log.info("组织筛选解析结束，解析后SQL：{}", newSql);
            return newSql;

        } catch (Exception e) {
            log.error("解析SQL异常{}", e.getMessage());
        }
        return null;
    }
    //判断实体类是否定义createDept字段
    public boolean containsDeptIdProperty(String tableName) {
        try {
            Object entity= SpringUtils.context().getBean(convert2BeanName(tableName));
            return entity.getClass().getDeclaredField(StringUtils.toCamelCase(deptIdColumn))!= null;
        } catch (NoSuchFieldException e) {
            return false;
        }
        catch (NoSuchBeanDefinitionException e){
            log.error("获取实体定义异常{}", e.getMessage());
            return false;
        }
    }

    //判断租户字段是否为数组
    private boolean isTenantArray(String tableName)  {
        try {
            Object entity = SpringUtils.context().getBean(convert2BeanName(tableName));
            javax.persistence.Column column = entity.getClass().getDeclaredField(StringUtils.toCamelCase(tenantIdColumn)).getAnnotation(javax.persistence.Column.class);
            if (column != null)
                if (column.columnDefinition().equals("json"))
                    return true;
        }catch (Exception e){
            log.error("isTenantArray异常{}", e.getMessage());
        }
        return false;
    }
    //获取第一个from表名
    private String getTableName(Statement statement){
        try {
            return ((Table) ((PlainSelect) ((Select) (statement)).getSelectBody()).getFromItem()).getName();
        }
        catch (Exception e){
            return null;
        }
    }
    //表名转类名
    private String convert2BeanName(String tableName) {
        // 分割表名中的下划线
        String[] parts = tableName.split("_");

        // 将每部分的首字母大写并连接起来
        StringBuilder className = new StringBuilder();
        for (String part : parts) {
            className.append(part.substring(0, 1).toUpperCase());
            if (part.length() > 1) {
                className.append(part.substring(1));
            }
        }

        String name= className.toString();
        return "tb"+name;//tbDeviceInfo
        //return name.substring(0, 1).toLowerCase() + name.substring(1);
    }


    private String processParser(Statement statement) {
        /*if (statement instanceof Insert) {
            this.processInsert((Insert) statement);
        } else if (statement instanceof Select) {
            this.processSelectBody(((Select) statement).getSelectBody());
        } else if (statement instanceof Update) {
            this.processUpdate((Update) statement);
        } else if (statement instanceof Delete) {
            this.processDelete((Delete) statement);
        }*/
        /**
         * 返回处理后的SQL
         */
        if (statement instanceof Select) {
            String tableName=getTableName(statement);
            if(tableName!=null) {
                log.info("表名：{}", tableName);
                boolean hasDeptId = containsDeptIdProperty(tableName);
                if(hasDeptId){
                    this.processSelectBody(((Select) statement).getSelectBody());
                }
            }

        }
        return statement.toString();
    }

    /**
     * select 语句处理
     */

    public void processSelectBody(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            processPlainSelect((PlainSelect) selectBody);
        } else if (selectBody instanceof WithItem) {
            WithItem withItem = (WithItem) selectBody;
            if (withItem.getSubSelect().getSelectBody() != null) {
                processSelectBody(withItem.getSubSelect().getSelectBody());
            }
        } else {
            SetOperationList operationList = (SetOperationList) selectBody;
            if (operationList.getSelects() != null && operationList.getSelects().size() > 0) {
                operationList.getSelects().forEach(this::processSelectBody);
            }
        }
    }

    /**
     * 处理 PlainSelect
     */
    protected void processPlainSelect(PlainSelect plainSelect) {
        if (plainSelect.getWhere() != null) {
            processPlainSelect(plainSelect, true);
        } else {
            processPlainSelect(plainSelect, false);
        }

    }

    /**
     * 处理 PlainSelect
     *
     * @param plainSelect ignore
     * @param addColumn   是否添加租户列,insert into select语句中需要
     */
    protected void processPlainSelect(PlainSelect plainSelect, boolean addColumn) {
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem instanceof Table) {
            Table fromTable = (Table) fromItem;
            //if (orgTables.contains(fromTable.getFullyQualifiedName())) {
                plainSelect.setWhere(builderExpression(plainSelect.getWhere(), fromTable));
                /*if (addColumn) {
                    plainSelect.getSelectItems().add(new SelectExpressionItem(new Column(deptIdColumn)));
                }*/
            //}
        } else {
            processFromItem(fromItem);
        }
        List<Join> joins = plainSelect.getJoins();
        if (joins != null && joins.size() > 0) {
            joins.forEach(j -> {
                processJoin(j);
                processFromItem(j.getRightItem());
            });
        }
    }

   /* *//**
     * insert 语句处理
     *//*

    public void processInsert(Insert insert) {
        if (orgTables.contains(insert.getTable().getFullyQualifiedName())) {
            insert.getColumns().add(new Column(deptIdColumn));
            if (insert.getSelect() != null) {
                processPlainSelect((PlainSelect) insert.getSelect().getSelectBody(), true);
            } else if (insert.getItemsList() != null) {
                // fixed github pull/295
                ItemsList itemsList = insert.getItemsList();
                if (itemsList instanceof MultiExpressionList) {
                    ((MultiExpressionList) itemsList).getExprList().forEach(el -> el.getExpressions().add(new StringValue(orgId)));
                } else {
                    ((ExpressionList) insert.getItemsList()).getExpressions().add(new StringValue(orgId));
                }
            } else {
                throw new RuntimeException("Failed to process multiple-table update, please exclude the tableName or statementId");
            }
        }
    }

    *//**
     * update 语句处理
     *//*

    public void processUpdate(Update update) {
        final Table table = update.getTable();
        if (orgTables.contains(table.getFullyQualifiedName())) {
            update.setWhere(this.andExpression(table, update.getWhere()));
        }
    }

    *//**
     * delete 语句处理
     *//*

    public void processDelete(Delete delete) {
        if (orgTables.contains(delete.getTable().getFullyQualifiedName())) {
            delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
        }
    }

    *//**
     * delete update 语句 where 处理
     *//*
    protected BinaryExpression andExpression(Table table, Expression where) {
        //获得where条件表达式
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(this.getAliasColumn(table));
        equalsTo.setRightExpression(new StringValue(orgId));
        if (null != where) {
            if (where instanceof OrExpression) {
                return new AndExpression(equalsTo, new Parenthesis(where));
            } else {
                return new AndExpression(equalsTo, where);
            }
        }
        return equalsTo;
    }
*/

    /**
     * 处理子查询等
     */
    protected void processFromItem(FromItem fromItem) {
        if (fromItem instanceof SubJoin) {
            SubJoin subJoin = (SubJoin) fromItem;
            if (subJoin.getJoinList() != null) {
                subJoin.getJoinList().forEach(this::processJoin);
            }
            if (subJoin.getLeft() != null) {
                processFromItem(subJoin.getLeft());
            }
        } else if (fromItem instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) fromItem;
            if (subSelect.getSelectBody() != null) {
                processSelectBody(subSelect.getSelectBody());
            }
        } else if (fromItem instanceof ValuesList) {
            log.debug("Perform a subquery, if you do not give us feedback");
        } else if (fromItem instanceof LateralSubSelect) {
            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
            if (lateralSubSelect.getSubSelect() != null) {
                SubSelect subSelect = lateralSubSelect.getSubSelect();
                if (subSelect.getSelectBody() != null) {
                    processSelectBody(subSelect.getSelectBody());
                }
            }
        }
    }

    /**
     * 处理联接语句
     */
    protected void processJoin(Join join) {
        if (join.getRightItem() instanceof Table) {
            Table fromTable = (Table) join.getRightItem();
            boolean hasDeptId = containsDeptIdProperty(fromTable.getFullyQualifiedName());
            if(hasDeptId){
                Expression expression=builderExpression(join.getOnExpression(), fromTable);
                if(expression!=null) {
                    join.getOnExpressions().removeAll(join.getOnExpressions());//解决on条件无法覆盖的问题
                    join.setOnExpression(expression);
                }
            }
        }
    }

    /***
     * 根据用户角色权限，构建查询条件
     * @param currentExpression
     * @param table
     * @return
     */
    private Expression builderDataPermissionExpression(Expression currentExpression, Table table){
        Expression expression = null;
        final InExpression inExpression = new InExpression();
        List<Expression> expressions = new ArrayList<>();
        log.info("builderDataPermissionExpression:"+table.getName()+":"+roleDeptPermission.get().toString());
        //如果当前表不需要部门权限拼接
        if(ignoreDeptEntitys.contains(table.getName()))
            roleDeptPermission.set(new HashSet<>());

        //添加所有部门id，如果其中有一个是ALL,表示可以查看所有数据退出循环
        for(String id : roleDeptPermission.get()){
            if(id.equals("ALL")){
                expression = null;
                break;
            }
            expressions.add(new StringValue(id));
        }
        log.info("builderDataPermissionExpression:expressions:"+expressions.toString());
        //拼接in条件（create_dept in('1','2')
        if(expressions.size()>0) {
            ExpressionList expressionList = new ExpressionList(expressions);
            inExpression.setLeftExpression(this.getAliasColumn(table));
            inExpression.setRightItemsList(expressionList);
            expression = inExpression;
        }
        //每个用户可以看到自己的数据 （or uid=1001)
        EqualsTo uidEqualsTo = new EqualsTo();
        uidEqualsTo.setLeftExpression(this.getOwerUserIdAliasColumn(table));
        uidEqualsTo.setRightExpression(new StringValue(LoginHelper.getUserId().toString()));
        //添加租户判断
        EqualsTo tenantEqualsTo = new EqualsTo();
        Function jsonContains = new Function();
        boolean isTenantArray=isTenantArray(table.getName());
        //判断租户字体是否为json类型
        if(isTenantArray){
            // 创建JSON_CONTAINS条件
            jsonContains.setName("JSON_CONTAINS");

            // 构造 JSON_ARRAY(561920499429445)
            Function jsonArray = new Function();
            jsonArray.setName("JSON_ARRAY");
            jsonArray.setParameters(new ExpressionList(new LongValue(LoginHelper.getTenantId())));

            // 设置 JSON_CONTAINS(tenant_id, JSON_ARRAY(561920499429445), '$')
            jsonContains.setParameters(new ExpressionList(
                    new Column(this.getTenantIdAliasColumn(table).getColumnName()),
                    jsonArray,
                    new StringValue("$")
            ));
            tenantEqualsTo.setLeftExpression(jsonContains);
            //tenantEqualsTo.setRightExpression(new StringValue("true")); // 这可能需要根据具体情况调整
        }
        else {
            tenantEqualsTo.setLeftExpression(this.getTenantIdAliasColumn(table));
            tenantEqualsTo.setRightExpression(new StringValue(LoginHelper.getTenantId().toString()));
        }

        //AndExpression tenantAndUidExp=new AndExpression(uidEqualsTo,tenantEqualsTo);
        //拼接部门id和用户id条件
        if(expression==null)
            return isTenantArray?jsonContains: tenantEqualsTo;//根据租户是否json判断使用json_contains或tenant_id=?
        else {
            expression=new AndExpression(expression,isTenantArray?jsonContains: tenantEqualsTo);
            expression = new OrExpression(expression, uidEqualsTo);
            //生成带括号的条件 （A or B）
            Parenthesis orParenthesis = new Parenthesis(expression);
            expression=orParenthesis;
        }
        return expression;
    }

    /**
     * 处理条件:
     * 创建InExpression，即封装where create_dept in ('','')
     */
    protected Expression builderExpression(Expression currentExpression, Table table) {

        Expression organizationExpression = builderDataPermissionExpression(currentExpression,table);

        Expression appendExpression  =  null;
        if (!(organizationExpression instanceof SupportsOldOracleJoinSyntax)) {
            appendExpression = new EqualsTo();
            ((EqualsTo) appendExpression).setLeftExpression(this.getAliasColumn(table));
            ((EqualsTo) appendExpression).setRightExpression(organizationExpression);
        }
        if (currentExpression == null) {
            return organizationExpression;
        }else {
            appendExpression  = organizationExpression;
        }
        if (currentExpression instanceof BinaryExpression) {
            BinaryExpression binaryExpression = (BinaryExpression) currentExpression;
            doExpression(binaryExpression.getLeftExpression());
            doExpression(binaryExpression.getRightExpression());
        } else if (currentExpression instanceof InExpression) {
            InExpression inExp = (InExpression) currentExpression;
            ItemsList rightItems = inExp.getRightItemsList();
            if (rightItems instanceof SubSelect) {
                processSelectBody(((SubSelect) rightItems).getSelectBody());
            }
        }
        if (currentExpression instanceof OrExpression) {
            return new AndExpression(new Parenthesis(currentExpression), appendExpression);
        } else {
            return new AndExpression(currentExpression, appendExpression);
        }
    }
    protected void doExpression(Expression expression) {
        if (expression instanceof FromItem) {
            processFromItem((FromItem) expression);
        } else if (expression instanceof InExpression) {
            InExpression inExp = (InExpression) expression;
            ItemsList rightItems = inExp.getRightItemsList();
            if (rightItems instanceof SubSelect) {
                processSelectBody(((SubSelect) rightItems).getSelectBody());
            }
        }
    }


    /**
     * 部门权限字段别名设置
     * <p>tableName.orgId 或 tableAlias.orgId</p>
     *
     * @param table 表对象
     * @return 字段
     */
    protected Column getAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (null == table.getAlias()) {
            column.append(table.getName());
        } else {
            column.append(table.getAlias().getName());
        }
        column.append(".");
        column.append(deptIdColumn);
        return new Column(column.toString());
    }

    //所有者用户id别名
    protected Column getOwerUserIdAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (null == table.getAlias()) {
            column.append(table.getName());
        } else {
            column.append(table.getAlias().getName());
        }
        column.append(".");
        column.append(userIdColumn);
        return new Column(column.toString());
    }

    protected Column getTenantIdAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (null == table.getAlias()) {
            column.append(table.getName());
        } else {
            column.append(table.getAlias().getName());
        }
        column.append(".");
        column.append(tenantIdColumn);
        return new Column(column.toString());
    }
}
